#include "pe.h"
#include "simulator_sim.h"
#include <iostream>


LDSTUnit::LDSTUnit(PE *pe)
{
	m_memory_config = pe->m_mem_config;
	m_mf_allocator=pe->m_mem_fetch_allocator;
	m_config=pe->m_pe_config;
    m_icnt = pe->m_icnt;
    m_pe = pe;
    m_pe_id = pe->m_pe_id;
    #define STRSIZE 1024
    m_L1D = NULL;
    char L1D_name[STRSIZE];
    snprintf(L1D_name, STRSIZE, "L1D_%03d", m_pe_id);
    m_L1D = new L1Cache( L1D_name,
                         pe->m_pe_config->m_L1_data_config,
                         pe->m_pe_id,
                         NORMAL,
                         m_icnt,
                         m_mf_allocator,
                         IN_L1D_MISS_QUEUE, 
						 m_pe,
						 pe->m_simulator);
	idx1=0;
	idx2=0;
	num_access=0;
	num_access_hit=0;
}
void LDSTUnit::cycle()
{
	if(m_L1D->access_ready())
	{
		MemFetch *mf=m_L1D->next_access();
		Instruction *inst=mf->get_inst();
		m_pe->writeback_ins_queue.push(inst);
		std::cout<<"cycle:"<<m_pe->m_simulator->simulator_sim_cycle<<"  access ready, address:0X" <<std::dec<<mf->get_addr()<<std::endl;
		delete mf;	
	}

	if(!m_response_fifo.empty())
	{
		MemFetch *mf=m_response_fifo.front();
		if(mf->get_type()==WRITE_ACK){
			m_response_fifo.pop_front();
			//m_pe->ldst_ins_queue.pop();
			delete mf;
		}	
		else{
			if(m_L1D->fill_port_free())
			{
				m_L1D->fill(mf, m_pe->m_simulator->simulator_sim_cycle);
				std::cout<<"cycle:"<<m_pe->m_simulator->simulator_sim_cycle<<"  fill, address:0X" <<std::dec<<mf->get_addr()<<std::endl;
				m_response_fifo.pop_front();
			}
		}
	}
	
	if(!m_pe->ldst_ins_queue.empty()&&!m_pe->ldst_ins_queue.front()->is_executing)
	{
		auto ins=m_pe->ldst_ins_queue.front();
		MemAccess acc(ins->is_write?GLOBAL_ACC_W:GLOBAL_ACC_R, ins->imm+ins->src1.val, ins->is_write , 64);
		num_access++;
		MemFetch *mf = new MemFetch(acc, ins->is_write?WRITE_PACKET_SIZE:READ_PACKET_SIZE, NULL, m_pe->m_pe_id, m_pe->m_mem_config, m_pe->m_simulator->simulator_sim_cycle);
		mf->set_inst(ins);
		m_pe->ldst_ins_queue.pop();
		enum CacheRequestState state;
		std::list<CacheEvent> events;
		state = m_L1D->access(ins->imm+ins->src1.val, mf, m_pe->m_simulator->simulator_sim_cycle, events);
		std::cout<<"cycle:"<<m_pe->m_simulator->simulator_sim_cycle<<"  cache access, address:0X" <<std::dec<<ins->imm+ins->src1.val;
		bool write_sent=was_write_sent((events));
		bool read_sent=was_read_sent(events);
		if(state==HIT)
		{
			m_pe->writeback_ins_queue.push(ins);
			num_access_hit++;
			std::cout<<"  hit"<<std::endl;
			delete mf;
		}
		else if(state==RESERVATION_FAIL)
		{
			std::cout<<"  reservation fail"<<std::endl;
		}
		else if(state==MISS){
			ins->is_executing=true;
			std::cout<<"  miss"<<std::endl;
		}
	}	
	m_L1D->cycle();
}


bool LDSTUnit::response_buffer_full()
{
    return m_response_fifo.size() >= 32;
}

void LDSTUnit::fill( MemFetch *mf )
{
    mf->set_status(IN_SHADER_LDST_RESPONSE_FIFO, m_pe->m_simulator->simulator_sim_cycle);
    m_response_fifo.push_back(mf);
}

void LDSTUnit::flush(){
	// Flush L1D cache
	m_L1D->flush();
}

PE::PE(uint32_t pe_id, SIMULATOR *simulator, const SimulatorConfig *simulator_config)

{
	m_pe_id = pe_id;
	m_pe_config = simulator_config->m_pe_config;
	m_mem_config = simulator_config->m_memory_config;
	m_simulator = simulator;

	m_ins_cache_config = &m_pe_config->m_L1_ins_config;
	m_data_cache_config = &m_pe_config->m_L1_data_config;
	m_icnt = new PEMemoryInterface(this);
	m_mem_fetch_allocator = new PEMemFetchAllocator(0, m_mem_config);

	m_pe_sim_done=false;
	pc=0;

	m_L1 = new ReadOnlyCache("L1", *m_ins_cache_config, m_pe_id, m_icnt, IN_L1I_MISS_QUEUE, this);
	m_ldst_unit = new LDSTUnit(this);
	m_ins_fetched=new Instruction();
	loop_cnt=0;
	num_executed_ins=0;
	start_pc_address=0;
	for(int i=0;i<32;i++)
	{
		registers[i]=0;
		scoreboard[i]=true;
	}
	load_code();
}


void PE::init(PEConfig *pe_config)
{
}

void PE::load_code()
{
	std::string file_path;
	std::string s;
	std::ifstream infile;

	file_path = "../trace/vecadd.txt";

	infile.open(file_path.data());
	assert(infile.is_open());
	while (getline(infile, s))
	{
		if(s.find("#")==std::string::npos && s!="")
			ins_buffer.push_back(s);
	}
	num_ins=ins_buffer.size();
		
	infile.close();

}
void PEConfig::reg_options(option_parser_t opp)
{

	option_parser_register(opp, "-simulator_cache:read_only_l1", OPT_CSTR,
						   &m_L1_ins_config.m_config_string,
						   "read only cache config "
						   " {<nsets>:<bsize>:<assoc>} ",
						   "4:256:4,256:4");
	option_parser_register(opp, "-simulator_cache:dl1", OPT_CSTR,
						   &m_L1_data_config.m_config_string,
						   "l1 data config"
						   " {<nsets>:<bsize>:<assoc>} ",
						   "4:256:4,256:4");

	option_parser_register(opp, "-simulator_ejection_buffer_size",
						   OPT_UINT32, &m_ejection_buffer_size,
						   "number of packets in ejection buffer", "8");

	option_parser_register(opp, "-simulator_mem_unit_ports",
						   OPT_INT32, &m_mem_unit_ports,
						   "The number of memory transactions allowed per core cycle", "1");
}

bool PE::active()
{		
	if(m_simulator->simulator_sim_cycle>100000)
	{
		if(m_L1->active())
			printf("watchdog m_l1 always active\n");
		else if(pc<num_ins*4)
			printf("watchdog pc < num_ins*4\n");
		else if(!fetch_ins_queue.empty())
			printf("watchdog fetch_ins_queue alwalys has ins\n");
		else if(!issue_ins_queue.empty())
		{
			printf("watchdog issue_ins_queue alwalys has ins\n");
			std::cout<<"ins:"<<issue_ins_queue.front()->ins<<std::endl;
		}	
		else if(!fetch_operand_ins_queue.empty())
			printf("watchdog fetch_operand_ins_queue alwalys has ins\n");
		else if(!ldst_ins_queue.empty())
			printf("watchdog ldst_ins_queue always has ins\n");
		else if(!writeback_ins_queue.empty())
			printf("watchdog writeback_ins_queue alwalys has ins\n");
		return false;
	}
	return  m_L1->active()||pc<num_ins*4||!fetch_ins_queue.empty()||
		    !issue_ins_queue.empty() || !fetch_operand_ins_queue.empty() ||
			!ldst_ins_queue.empty()|| !alu_ins_queue.empty()||
			!writeback_ins_queue.empty();
}
void PE::accept_fetch_response(MemFetch *mf)
{
	mf->set_status(IN_SHADER_FETCHED, m_simulator->simulator_sim_cycle);
	m_L1->fill(mf, m_simulator->simulator_sim_cycle);
}
bool PE::ldst_unit_response_buffer_full()
{
	return m_ldst_unit->response_buffer_full();
}

void PE::icnt_cycle()
{
	if (!m_response_fifo.empty())
	{
		MemFetch *mf = m_response_fifo.front();
		if (mf->get_access_type() == INST_ACC_R)
		{
			// instruction fetch response
			if (!fetch_unit_response_buffer_full())
			{
				m_response_fifo.pop_front();
				accept_fetch_response(mf);
			}
		}
		else
		{
			if(!ldst_unit_response_buffer_full())
			{
				m_response_fifo.pop_front();
				m_ldst_unit->fill(mf);
			}
		}
	}
	if (m_response_fifo.size() < m_pe_config->m_ejection_buffer_size)
	{
		MemFetch *mf = (MemFetch *)::icnt_pop(m_pe_id);
		if (!mf) return;
		if(m_simulator->m_config->m_debug_timing)
      		printf("icnt push response to pe, addr:%ld\n", mf->get_addr());
		assert(mf->get_type() == READ_REPLY || mf->get_type() == WRITE_ACK);
		unsigned int packet_size = (mf->get_is_write()) ? mf->get_ctrl_size() : mf->size();
		mf->set_status(IN_CLUSTER_TO_SHADER_QUEUE, m_simulator->simulator_sim_cycle);
		m_response_fifo.push_back(mf);

	}
}

bool PE::icnt_injection_buffer_full(uint32_t size, bool write)
{
	uint32_t request_size = size;
	if (!write)
		request_size = READ_PACKET_SIZE;
	return !::icnt_has_buffer(m_pe_id, request_size);
}

void PE::icnt_inject_request_packet(MemFetch *mf)
{
	uint32_t destination = mf->get_sub_partition_id();
	mf->set_status(IN_ICNT_TO_MEM, m_simulator->simulator_sim_cycle);
	if (!mf->get_is_write())
		::icnt_push(m_pe_id, m_simulator->m_config->mem2device(destination), (void *)mf, mf->get_ctrl_size());
	else
		::icnt_push(m_pe_id, m_simulator->m_config->mem2device(destination), (void *)mf, mf->size());
}
void PE::writeback()
{
	if(!writeback_ins_queue.empty())
	{	
		Instruction *inst=writeback_ins_queue.front();
		if(inst->dst.type!=OpType::NONE)
		{
			scoreboard[inst->dst.reg_idx]=true;
			registers[inst->dst.reg_idx]=inst->result;
			//std::cout<<"sb:"<<inst->dst.reg_idx<<" true "<<inst->ins<<std::endl;
		}
		delete writeback_ins_queue.front();
		writeback_ins_queue.pop();
		
	}
}
void PE::execute()
{
	m_ldst_unit->cycle();
	if(!alu_ins_queue.empty())
	{
		Instruction *inst=alu_ins_queue.front();
		if(inst->type==InsType::BNE)
		{
			if(inst->src1.val!=inst->src2.val)
				pc=start_pc_address+inst->imm*4;
		}
		else
		{
			writeback_ins_queue.push((alu_ins_queue.front()));
			//std::cout<<"push inst to writeback queue:"<<inst->ins<<std::endl;
			if(inst->type==InsType::LUI)
				inst->result=inst->imm;
			else if(inst->type==InsType::ADDI)
				inst->result=inst->src1.val+inst->imm;
			else if(inst->type==InsType::ADD)
				inst->result=inst->src1.val+inst->src2.val;
		}
		alu_ins_queue.pop();
	}
}
void PE::fetch_operand()
{
	if(!fetch_operand_ins_queue.empty())
	{
		Instruction *inst=fetch_operand_ins_queue.front();
		if(inst->src1.type!=OpType::NONE)
			inst->src1.val=registers[inst->src1.reg_idx];
		if(inst->src2.type!=OpType::NONE)
			inst->src2.val=registers[inst->src2.reg_idx];
		if(inst->type==InsType::LW||inst->type==InsType::SW)
		{
			ldst_ins_queue.push(inst);
			//std::cout<<"push inst to ldst queue:"<<inst->ins<<std::endl;
		}
		else
			alu_ins_queue.push(inst);
		fetch_operand_ins_queue.pop();
	}	
}

void PE::issue()
{
	if(!issue_ins_queue.empty())
	{
		Instruction *inst=issue_ins_queue.front();
		bool scoreboard_check=true;
		if(inst->src1.type!=OpType::NONE && !scoreboard[inst->src1.reg_idx])
			scoreboard_check=false;
		if(inst->src2.type!=OpType::NONE && !scoreboard[inst->src2.reg_idx])
			scoreboard_check=false;
		if(inst->type!=InsType::BNE && inst->type!=InsType::SW &&inst->dst.type!=OpType::NONE && !scoreboard[inst->dst.reg_idx])
			scoreboard_check=false;
		if(scoreboard_check)
		{
			fetch_operand_ins_queue.push(inst);
			if(inst->type!=InsType::BNE&&inst->type!=InsType::SW&&inst->dst.type!=OpType::NONE)
			{
				scoreboard[inst->dst.reg_idx]=false;
				//std::cout<<"sb:"<<inst->dst.reg_idx<<" false "<<inst->ins<<std::endl;
			}
			issue_ins_queue.pop();
		}
	}
}

void PE::decode()
{
	if(!fetch_ins_queue.empty())
	{
		Instruction *inst=fetch_ins_queue.front();
		if(!inst->is_fetching)
		{
			inst->decode();
			issue_ins_queue.push(inst);
			fetch_ins_queue.pop();
		}
	}
}
void PE::fetch()
{
	if((fetch_ins_queue.empty()&&pc<num_ins*4)||(!fetch_ins_queue.empty()&&fetch_ins_queue.front()->is_fetching))
	{
		if(m_L1->access_ready())
		{
			MemFetch *mf=m_L1->next_access();
			Instruction *inst=fetch_ins_queue.front();
			inst->is_fetching=false;
			inst->pc=mf->get_addr();
			inst->ins=ins_buffer[m_ins_fetched->pc/4];
			delete mf;
		}
		else
		{
			if(pc<=num_ins*4&&fetch_ins_queue.empty())
			{
				MemAccess acc(INST_ACC_R, pc+start_pc_address, false, 64);
				num_executed_ins++;
				MemFetch *mf = new MemFetch(acc, READ_PACKET_SIZE, m_pe_id, m_mem_config, m_simulator->simulator_sim_cycle);
				enum CacheRequestState state;
				std::list<CacheEvent> events;
				state = m_L1->access(pc, mf, m_simulator->simulator_sim_cycle, events);
				if (state == MISS)
				{
					Instruction *inst_miss=new Instruction();
					inst_miss->is_fetching=true;
					fetch_ins_queue.push(inst_miss);
					pc+=4;
				}
				else if (state == HIT)
				{
					Instruction *inst_hit=new Instruction();
					inst_hit->valid=true;
					inst_hit->pc=mf->get_addr();
					inst_hit->ins=ins_buffer[inst_hit->pc/4];
					//printf("inst hit pc:%d\n", inst_hit->pc);
					fetch_ins_queue.push(inst_hit);
					pc+=4;
					delete mf;
				}
				else
				{
					printf("else\n");
				}
			}
		}
	}
	m_L1->cycle();
}

void PE::cycle()
{
	writeback();
	execute();
	fetch_operand();
	issue();
	decode();
	fetch();
}
